package com.kamistoat.meimeistore.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kamistoat.common.To.EsTo.OrderItemEntityTo;
import com.kamistoat.common.To.EsTo.OrderListTo;
import com.kamistoat.common.To.EsTo.SubOrderTo;
import com.kamistoat.common.To.LoginSuccessTo;
import com.kamistoat.common.To.RabbitMQTo.OrderTo;
import com.kamistoat.common.To.RabbitMQTo.SecKillOrderTo;
import com.kamistoat.common.To.SecKillTo.SecKillProductCacheTo;
import com.kamistoat.common.To.SecKillTo.SecKillProductTo;
import com.kamistoat.common.To.SecKillTo.SecKillSkuInfoTo;
import com.kamistoat.common.constant.CartConstant;
import com.kamistoat.common.constant.SecKillConstant;
import com.kamistoat.common.exception.ALLRunTimeException;
import com.kamistoat.common.utils.R;
import com.kamistoat.meimeistore.order.To.OrderCreateTo;
import com.kamistoat.meimeistore.order.entity.*;
import com.kamistoat.meimeistore.order.entity.*;
import com.kamistoat.meimeistore.order.enume.OrderStatusEnum;
import com.kamistoat.meimeistore.order.feign.*;
import com.kamistoat.meimeistore.order.feign.*;
import com.kamistoat.meimeistore.order.interceptor.LoginInterceptor;
import com.kamistoat.meimeistore.order.service.*;
import com.kamistoat.meimeistore.order.vo.*;
import com.kamistoat.meimeistore.order.service.*;
import com.kamistoat.meimeistore.order.vo.*;
import org.redisson.api.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kamistoat.common.utils.PageUtils;
import com.kamistoat.common.utils.Query;

import com.kamistoat.meimeistore.order.dao.OrderDao;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {


    @Autowired
    MemberFeignService memberFeignService;
    @Autowired
    CartFeignService cartFeignService;
    @Autowired
    ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    WareFeignService wareFeignService;
    @Autowired
    CouponFeignService couponFeignService;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    OrderService orderService;
    @Autowired
    SubOrderService subOrderService;
    @Autowired
    OrderSubOrderRelationService orderSubOrderRelationService;
    @Autowired
    OrderItemService orderItemService;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    PaymentInfoService paymentInfoService;
    @Autowired
    OrderDao orderDao;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 当用户点击购物车结算时，查出订单检查页面所需要的所有数据
     * 注意，必须去redis的购物车信息中重新查询一遍用户选中的购物项，然后再重新查询一遍是否有货
     *
     * @return
     */
    @Override
    public OrderCheckVo checkOrder() {
        OrderCheckVo orderCheckVo = new OrderCheckVo();
        // 先从ThreadLocal中把用户信息拿出来
        LoginSuccessTo loginSuccessTo = LoginInterceptor.loginUserThreadLocal.get();
        // 把主线程中的上下文信息提取出来，在异步任务时强制同步
        RequestAttributes mainThreadContext = RequestContextHolder.getRequestAttributes();

        CompletableFuture<Void> getOrderAddressAsync = CompletableFuture.runAsync(() -> {
            // 先去redis中查询缓存
            String memberAddressVoListVoString = stringRedisTemplate.opsForValue().get("member:address:list:" + loginSuccessTo.getId());
            if (memberAddressVoListVoString != null && !memberAddressVoListVoString.isEmpty()) {
                MemberAddressVoListVo memberAddressVoListVo = JSON.parseObject(memberAddressVoListVoString, MemberAddressVoListVo.class);
                orderCheckVo.setMemberAddressVoList(memberAddressVoListVo.getMemberAddressVoList());
            } else {
                // 否则使用远程调用接口查询用户的所有收货地址。
                // 强制将异步线程的数据和主线程数据同步。注意，这个RequestContextHolder和上面主线程中的RequestContextHolder已经不是同一个了
                RequestContextHolder.setRequestAttributes(mainThreadContext);
                List<MemberAddressVo> memberAddressVoList = memberFeignService.getOrderAddress(loginSuccessTo.getId());
                orderCheckVo.setMemberAddressVoList(memberAddressVoList);
                // 地址入缓存
                MemberAddressVoListVo memberAddressVoListVo = new MemberAddressVoListVo();
                memberAddressVoListVo.setMemberAddressVoList(memberAddressVoList);
                stringRedisTemplate.opsForValue().set("member:address:list:" + loginSuccessTo.getId(), JSON.toJSONString(memberAddressVoListVo));
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> getOrderCartCheckedAsync = CompletableFuture.runAsync(() -> {
            // 强制将异步线程的数据和主线程数据同步。注意，这个RequestContextHolder和上面主线程中的RequestContextHolder已经不是同一个了
            RequestContextHolder.setRequestAttributes(mainThreadContext);
            // 远程调用接口查询用户购物车中被选中的购物项
            List<OrderItemVo> orderItemVoList = cartFeignService.getOrderCartChecked();
            orderCheckVo.setOrderItemVoList(orderItemVoList);
        }, threadPoolExecutor).thenRunAsync(() -> {
            // 紧接着查询所有商品的库存情况
            List<OrderItemVo> orderItemVoList = orderCheckVo.getOrderItemVoList();
            List<Long> idList = orderItemVoList.stream().map(orderItemVo -> {
                return orderItemVo.getSkuId();
            }).collect(Collectors.toList());
            R r = wareFeignService.hasStock(idList);
            Map<String, Boolean> hasStockMap = (Map<String, Boolean>) r.get("data");
            orderCheckVo.setHasStockMap(hasStockMap);
        }, threadPoolExecutor);

        CompletableFuture<Void> setOrderToken = CompletableFuture.runAsync(() -> {
            // 删除防重令牌的内容，在订单检查页面不再需要防重，京东是完全允许用户在此重复刷新重复获取订单确认内容
            // 我们改为在调用toTrade时，使用redisson限流器进行限流
//            String token = UUID.randomUUID().toString().replace("-", "");
//            orderCheckVo.setOrderToken(token);
//            // 将防重令牌保存到redis中。在5分钟之内不能在同一个页面上提交相同订单
//            stringRedisTemplate.opsForValue().set(
//                    OrderConstant.ORDER_TOKEN_PREFIX + loginSuccessTo.getId(),
//                    token,
//                    OrderConstant.ORDER_TOKEN_EXPIRE,
//                    OrderConstant.ORDER_TOKEN_EXPIRE_UNIT);
        }, threadPoolExecutor);

        // 用户积分，直接从ThreadLocal中拿出来即可
        orderCheckVo.setIntegration(loginSuccessTo.getIntegration());

        try {
            CompletableFuture.allOf(getOrderAddressAsync, getOrderCartCheckedAsync, setOrderToken).get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        return orderCheckVo;
    }

    /**
     * 如果一个商品正在参与秒杀，则当点击抢购时，仅仅验证信号量锁是否放行。
     * 通过后调用此URL，跳转到订单确认页，此时已不是直面全部流量了。则可以对秒杀数据进行全面校验。
     * <p>
     * 但是为了提高性能，此处极力避免了数据库操作
     * <p>
     * 专门给秒杀商品用的，和上面的普通toTrade区分
     *
     * @return
     */
    @Override
    public R checkSeckillOrder(Long sessionProId, String randomCode, Long skuId, Integer wantNum) {
        OrderCheckVo orderCheckVo = new OrderCheckVo();
        // 表明是秒杀订单
        orderCheckVo.setIsSeckill(true);
        // 先从ThreadLocal中把用户信息拿出来
        LoginSuccessTo loginSuccessTo = LoginInterceptor.loginUserThreadLocal.get();
        // 把主线程中的上下文信息提取出来，在异步任务时强制同步
        RequestAttributes mainThreadContext = RequestContextHolder.getRequestAttributes();

        // 从redis中查出场次-商品关联信息，验证skuId
        BoundHashOperations<String, String, String> sessionProHash =
                stringRedisTemplate.boundHashOps(SecKillConstant.SEC_PRODUCT_PREFIX);
        String cacheVoJSON = sessionProHash.get(sessionProId.toString());
        if (StringUtils.isEmpty(cacheVoJSON)) {
            // redis中已经没有这个秒杀商品了
            return R.error("该秒杀活动已结束");
        } else {
            SecKillProductCacheTo cacheVo = JSON.parseObject(cacheVoJSON, SecKillProductCacheTo.class);
            if (!cacheVo.getSecKillProductTo().getSkuId().equals(skuId)) {
                // 合法性校验失败。skuId对不上
                return R.error("合法性校验失败:该商品不在当前秒杀活动中，请严格从秒杀商品详情页进入秒杀活动");
            } else {
                Long startTime = cacheVo.getStartTime();
                Long endTime = cacheVo.getEndTime();
                long curtime = new Date().getTime();
                if (curtime < startTime || curtime > endTime) {
                    // 合法性校验失败。时间不对
                    return R.error("合法性校验失败:该秒杀活动尚未开始，请严格从秒杀商品详情页进入秒杀活动");
                } else if (!cacheVo.getRandomCode().equals(randomCode)) {
                    // 合法性校验失败，随机码不对
                    return R.error("合法性校验失败:您提交了错误的商品秘钥，请严格从秒杀商品详情页进入秒杀活动");
                } else if (cacheVo.getSecKillProductTo().getSeckillLimit() <= wantNum) {
                    // 合法性校验失败，单次购买超过限制
                    return R.error("合法性校验失败:单次购买数量超出限额");
                } else {
                    // 验证当前用户累计购买是否超过限制，同样使用信号量锁实现
                    String redisKey = SecKillConstant.SEC_PRO_USER + sessionProId.toString() + "_" + loginSuccessTo.getId().toString();
                    String userCount = stringRedisTemplate.opsForValue().get(redisKey);
                    if (!StringUtils.isEmpty(userCount) &&
                            Integer.parseInt(userCount) + wantNum > cacheVo.getSecKillProductTo().getSeckillLimit()) {
                        return R.error("合法性校验失败:您购买此商品的数量已达到限额，请减少购买数量");
                    } else {
                        // 记录下用户最新的累计购买数量。注意此时还不能直接修改redis，因为现在也不能确定用户购买成功
                        userCount = !StringUtils.isEmpty(userCount) ? String.valueOf((Integer.parseInt(userCount) + wantNum)) : wantNum.toString();
                        // 开始查询构建订单所需的信息
                        // 1. 查询收货地址
                        CompletableFuture<Void> getOrderAddressAsync = CompletableFuture.runAsync(() -> {
                            // 先去redis中查询缓存
                            String memberAddressVoListVoString = stringRedisTemplate.opsForValue().get("member:address:list:" + loginSuccessTo.getId());
                            if (memberAddressVoListVoString != null && !memberAddressVoListVoString.isEmpty()) {
                                MemberAddressVoListVo memberAddressVoListVo = JSON.parseObject(memberAddressVoListVoString, MemberAddressVoListVo.class);
                                orderCheckVo.setMemberAddressVoList(memberAddressVoListVo.getMemberAddressVoList());
                            } else {
                                // 否则使用远程调用接口查询用户的所有收货地址。
                                // 强制将异步线程的数据和主线程数据同步。注意，这个RequestContextHolder和上面主线程中的RequestContextHolder已经不是同一个了
                                RequestContextHolder.setRequestAttributes(mainThreadContext);
                                List<MemberAddressVo> memberAddressVoList = memberFeignService.getOrderAddress(loginSuccessTo.getId());
                                orderCheckVo.setMemberAddressVoList(memberAddressVoList);
                                // 地址入缓存
                                MemberAddressVoListVo memberAddressVoListVo = new MemberAddressVoListVo();
                                memberAddressVoListVo.setMemberAddressVoList(memberAddressVoList);
                                stringRedisTemplate.opsForValue().set("member:address:list:" + loginSuccessTo.getId(), JSON.toJSONString(memberAddressVoListVo));
                            }
                        }, threadPoolExecutor);

                        // 2. 保存商品详细信息。注意这里不是去数据库查，而是直接使用缓存中的秒杀信息
                        CompletableFuture<Void> buildSkuInfoAsync = CompletableFuture.runAsync(() -> {
                            OrderItemVo orderItemVo = new OrderItemVo();
                            SecKillProductTo secKillProductInfo = cacheVo.getSecKillProductTo();
                            SecKillSkuInfoTo secKillOriInfo = cacheVo.getSecKillSkuInfoTo();
                            // 保存订单小项详情
                            orderItemVo.setSkuId(secKillOriInfo.getSkuId());
                            orderItemVo.setTitle(secKillOriInfo.getSkuTitle());
                            orderItemVo.setImage(secKillOriInfo.getSkuDefaultImg());
                            orderItemVo.setSkuAttr(secKillOriInfo.getSkuAttr());
                            // 商品价格使用秒杀价格
                            orderItemVo.setPrice(secKillProductInfo.getSeckillPrice());
                            orderItemVo.setCount(wantNum);
                            orderItemVo.setHasStock(true);
                            orderItemVo.setBrandId(secKillOriInfo.getBrandId());
                            orderItemVo.setBrandName(secKillOriInfo.getBrandName());
                            // 写进对象
                            orderCheckVo.setOrderItemVoList(Arrays.asList(orderItemVo));
                        }, threadPoolExecutor);

                        // 3. 杂项信息
                        CompletableFuture<Void> utilsInfoAsync = CompletableFuture.runAsync(() -> {
                            // 库存并不去数据库查询，而是直接认为有库存，因此此时并不做库存限制，只要用了信号量锁一定可以保证库存的幂等性
                            Map<String, Boolean> hasStock = new HashMap<>();
                            hasStock.put(skuId.toString(), true);
                            orderCheckVo.setHasStockMap(hasStock);
                            // 用户积分，直接从ThreadLocal中拿出来即可
                            orderCheckVo.setIntegration(loginSuccessTo.getIntegration());
                            // 保存秒杀相关参数，用于验证合法性
                            orderCheckVo.setSessionProId(sessionProId);
                            orderCheckVo.setRandomCode(randomCode);
                        }, threadPoolExecutor);

                        // 不再进行其他的商品信息校验。

                        try {
                            CompletableFuture.allOf(getOrderAddressAsync, buildSkuInfoAsync, utilsInfoAsync).get();
                        } catch (InterruptedException | ExecutionException e) {
                            e.printStackTrace();
                        }

                        R orderCheckVo_R = R.ok();
                        orderCheckVo_R.put("orderCheckVo", orderCheckVo);
                        return orderCheckVo_R;
                    }
                }
            }
        }
    }


    /**
     * 结算页进入下单页支付
     * 用户点击提交订单。注意 OrderSubmitVo 只含有有限的信息，到底买了那些东西会再次去数据库进行验证
     *
     * @param orderSubmitVo
     * @return
     */
    @Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {
        // 创建一个返回类，并从ThreadLocal中把拦截到的用户信息提取出来
        SubmitOrderResponseVo submitOrderResponseVo = new SubmitOrderResponseVo();
        LoginSuccessTo loginSuccessTo = LoginInterceptor.loginUserThreadLocal.get();

        // TODO 原本的做法是验证令牌，迸发性为0.现在改成不验证令牌，只是限制同时只能提交一个订单
//        // 1验证令牌
//        // 其核心是--令牌的验证和删除必须保持原子性
//        // 使用LUA脚本来执行，从而保证redis的原子性操作，而不是使用手动的redisTemplates去操作
//        // 这个脚本的返回结果是Long类型的0和1。0：当KEYS[1]不存在 或 删除KEYS[1]失败。1：KEYS[1]存在且和ARGV[1]相等并成功删除
//        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//        String orderToken = orderSubmitVo.getOrderToken();
//        // 第二个参数就是输入的KEYS数组，必须是数组
//        // 第三个第四个第五个参数共同组成ARGV数组，每个参数都是ARGV数组中的第几个元素
//        Long tokenResult = stringRedisTemplate.execute(
//                new DefaultRedisScript<Long>(luaScript, Long.class),
//                Arrays.asList(OrderConstant.ORDER_TOKEN_PREFIX + loginSuccessTo.getId()),
//                orderToken);
//        if (tokenResult == 0L) {
//            // 令牌验证失败.状态码为1
//            // 要么重复令牌， 要么删除令牌失败(一般不会出现这种情况)
//            submitOrderResponseVo.setCode(1);
//            return submitOrderResponseVo;

        // 获取分布式锁，直接限死同一时刻只允许一个同账号的登录实例submit订单
        RLock submitLock = redissonClient.getLock("order:orderSubmit:rLock:" + loginSuccessTo.getId());
        try {
            // 只做尝试并不等待，一旦阻塞立刻返回
            Boolean submitTry = submitLock.tryLock(10, TimeUnit.SECONDS);
            // 没有获取到锁，说明存在多个登录实例同时提交订单，则直接返回
            if (!submitTry) {
                submitOrderResponseVo.setCode(1);
                return submitOrderResponseVo;
            } else {
                // 通过验证才执行下单的真正操作
                // 1.根据提交订单页面传递来的有限的信息，再去数据库购物车把打钩项商品查一遍，再构建一个订单，这是最后一次和购物车同步了
                OrderCreateTo orderCreateTo = createOrderBySubmit(orderSubmitVo);
                // 2.进行价格验证。
                // TODO 这里有个bug，就是万一购物车确实改变了，但改变后价格相同怎么办，这里就放过了。
                //  但也不算bug，因为就算放过了，下面全部的操作都是基于最新购物车做的，不会有大问题。
                BigDecimal payAmount = orderCreateTo.getOrderEntity().getPayAmount();
                BigDecimal truePrice = orderSubmitVo.getTruePrice();
                // 同步前后端小数点位数不同的问题
                if (Math.abs(payAmount.subtract(truePrice).doubleValue()) < 0.01) {
                    // 3对比成功，向数据库保存 未经拆分的总订单A
                    // TODO 这里还是用的直接向数据库保存，没有用消息队列，可能存在高迸发问题
                    saveOrder(orderCreateTo);
                    // 4保存成功锁定库存。只要有异常回滚订单数据
                    WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
                    wareSkuLockVo.setOrderSn(orderCreateTo.getOrderEntity().getOrderSn());  // 订单的sn

                    // 保存这次订单中所有商品小项的购买详情，ware模块将根据这些信息依次去锁定每一个商品的库存
                    List<OrderLockVo> orderLockVoList = orderCreateTo.getOrderItemEntityList().stream().map(orderItemEntity -> {
                        OrderLockVo orderLockVo = new OrderLockVo();
                        orderLockVo.setSkuId(orderItemEntity.getSkuId());
                        orderLockVo.setCount(orderItemEntity.getSkuQuantity());
                        orderLockVo.setTitle(orderItemEntity.getSkuName());
                        return orderLockVo;
                    }).collect(Collectors.toList());
                    wareSkuLockVo.setLockList(orderLockVoList);  // 订单中所有商品小项的信息，放到wareSkuLockVo中

                    // 调用远程接口去锁定库存。转到ware模块的 WareSkuController下的orderLockStock方法。
                    // 如果远程锁定成功则返回R.ok(),否则返回带有失败skuId的error
                    R r = wareFeignService.orderLockStock(wareSkuLockVo);

                    if ((int) r.get("code") == 0) {
                        // 锁库存成功，整个结算页转支付页完成
                        submitOrderResponseVo.setOrderEntity(orderCreateTo.getOrderEntity());
                        // TODO 当然如果想的话，还可以在这个if之下继续加其他的功能。比如说扣减积分等等

                        // 在所有都结束后，订单真的成功了。还要做两个事情:
                        // 1. 删除购物车中打钩商品
                        // 2. 给RabbitMQ中传一个消息，消息内容就是一个类见传输类OrderTo

                        // 删除购物车打钩商品
                        BoundHashOperations<String, String, String> cartHashBound =
                                stringRedisTemplate.boundHashOps(CartConstant.CART_PREFIX + loginSuccessTo.getId());
                        Object[] skuArrays = orderLockVoList.stream().map(orderLockVo -> {
                            return orderLockVo.getSkuId().toString();
                        }).toArray();
                        cartHashBound.delete(skuArrays);

                        // 向rabbit传延时订单的消息
                        OrderTo orderTo = new OrderTo();
                        orderTo.setOrderSn(orderCreateTo.getOrderEntity().getOrderSn());
                        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", orderTo);

                        // 成功下单，进入支付等待
                        submitOrderResponseVo.setCode(0);
                        return submitOrderResponseVo;
                    } else {
                        // 锁库存失败。回滚刚刚已经保存到数据库中的数据
                        // 注意这里抛出异常，能够回滚的数据仅仅是 订单orderCreateTo而已，是没有办法回滚库存的，因为回滚库存需要在ware模块中进行
                        Long skuId = (Long) r.get("skuId");
                        throw new ALLRunTimeException.NoStockException(skuId);
                    }
                } else {
                    // 价格校验失败
                    submitOrderResponseVo.setCode(2);
                    return submitOrderResponseVo;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            return submitOrderResponseVo;
        } finally {
            submitLock.unlock();
        }
    }

    /**
     * 秒杀订单提交订单。全面使用消息队列进行服务解耦
     *
     * @param orderSubmitVo
     * @return 等待支付页。但是注意，秒杀高并发下，只能保证缓存中一定存在订单，数据库中订单是否入库未知
     */
    @Override
    public R submitSeckillOrder(OrderSubmitVo orderSubmitVo) {
        // 在业务代码前验证信号量，如果售罄则不进行接下来的内容
        RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SEC_KILL_SEMAPHORE + orderSubmitVo.getRandomCode());
        if (semaphore == null || semaphore.availablePermits() == 0) {
            return R.error("秒杀商品已售罄");
        }

        // 先从ThreadLocal中把用户信息拿出来
        LoginSuccessTo loginSuccessTo = LoginInterceptor.loginUserThreadLocal.get();
        // 把主线程中的上下文信息提取出来，用于在异步任务时强制同步
        RequestAttributes mainThreadContext = RequestContextHolder.getRequestAttributes();
        // 这里对秒杀信息做最后一次验证，依然是为了防止有人直接从URL发请求过来。这里是最后一次验证了

        // 从redis中查出场次-商品关联信息，验证skuId
        BoundHashOperations<String, String, String> sessionProHash =
                stringRedisTemplate.boundHashOps(SecKillConstant.SEC_PRODUCT_PREFIX);
        String cacheVoJSON = sessionProHash.get(orderSubmitVo.getSessionProId().toString());
        if (StringUtils.isEmpty(cacheVoJSON)) {
            // redis中已经没有这个秒杀商品了
            return R.error("该秒杀活动已结束");
        } else {
            SecKillProductCacheTo cacheVo = JSON.parseObject(cacheVoJSON, SecKillProductCacheTo.class);
            if (!cacheVo.getSecKillProductTo().getSkuId().equals(orderSubmitVo.getSkuId())) {
                // 合法性校验失败。skuId对不上
                return R.error("合法性校验失败:该商品不在当前秒杀活动中，请严格从秒杀商品详情页进入秒杀活动");
            } else {
                Long startTime = cacheVo.getStartTime();
                Long endTime = cacheVo.getEndTime();
                long curtime = new Date().getTime();
                if (curtime < startTime || curtime > endTime) {
                    // 合法性校验失败。时间不对
                    return R.error("合法性校验失败:该秒杀活动尚未开始，请严格从秒杀商品详情页进入秒杀活动");
                } else if (!cacheVo.getRandomCode().equals(orderSubmitVo.getRandomCode())) {
                    // 合法性校验失败，随机码不对
                    return R.error("合法性校验失败:您提交了错误的商品秘钥，请严格从秒杀商品详情页进入秒杀活动");
                } else if (cacheVo.getSecKillProductTo().getSeckillLimit() <= orderSubmitVo.getWantNum()) {
                    // 合法性校验失败，单次购买超过限制
                    return R.error("合法性校验失败:单次购买数量超出限额");
                } else {
                    // 验证当前用户累计购买是否超过限制，同样使用信号量锁实现
                    String redisKey = SecKillConstant.SEC_PRO_USER + orderSubmitVo.getSessionProId().toString() + "_" + loginSuccessTo.getId().toString();
                    String userCount = stringRedisTemplate.opsForValue().get(redisKey);
                    if (!StringUtils.isEmpty(userCount) &&
                            Integer.parseInt(userCount) + orderSubmitVo.getWantNum() > cacheVo.getSecKillProductTo().getSeckillLimit()) {
                        return R.error("合法性校验失败:您购买此商品的数量已达到限额，请减少购买数量");
                    } else {
                        // 记录下用户最新的累计购买数量。注意此时还不能直接修改redis，因为现在也不能确定用户购买成功
                        userCount = !StringUtils.isEmpty(userCount) ?
                                String.valueOf((Integer.parseInt(userCount) + orderSubmitVo.getWantNum())) :
                                orderSubmitVo.getWantNum().toString();
                        // 尝试扣减信号量锁，注意是扣减，这里是真的在秒杀了
                        try {
                            boolean tryAcquire = semaphore.tryAcquire(orderSubmitVo.getWantNum(), 1, TimeUnit.SECONDS);
                            if (!tryAcquire) {
                                return R.error("该商品已售罄");
                            } else {
                                // 此时秒杀成功。
                                // 设置用户累计购买数量
                                stringRedisTemplate.opsForValue().set(redisKey, userCount);
                                // 处理秒杀成功事件。包括构造秒杀总订单入缓存，消息队列发送消息.
                                // 这里是阻塞等待，因为必须明确是否成功
                                try {
                                    orderSubmitVo.setSecKillProductCacheTo(cacheVo);
                                    OrderCreateTo orderCreateTo = handleSuccessSecKill(orderSubmitVo);
                                    return R.ok().put("order", orderCreateTo);
                                } catch (Exception e) {
                                    // 此时如果异常则应该回滚信号量
                                    semaphore.addPermits(orderSubmitVo.getWantNum());
                                    e.printStackTrace();
                                    return R.error("系统未知异常，秒杀失败");
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            return R.error("库存未知异常，秒杀失败");
                        }
                    }
                }
            }
        }
    }

    /**
     * 处理秒杀成功事件。包括构造秒杀总订单，入缓存，消息队列发送消息等。
     * 直接对接原有的订单生成系统。只是不会进行购物车检查。
     */
    private OrderCreateTo handleSuccessSecKill(OrderSubmitVo orderSubmitVo) {
        LoginSuccessTo loginSuccessTo = LoginInterceptor.loginUserThreadLocal.get();
        // 构建订单
        OrderCreateTo orderCreateTo = createOrderBySubmit(orderSubmitVo);
        // 订单入缓存, 使用 seckill:order:未拆分总订单的orderSN:userId 作为key
        stringRedisTemplate.opsForValue().set("seckill:order:" + orderCreateTo.getOrderEntity().getOrderSn() + ":" + loginSuccessTo.getId(),
                JSON.toJSONString(orderCreateTo));
        // 该队列用于将未支付状态的订单入库
        rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.new.order", orderCreateTo);
        // 该队列用于检测超时订单
        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", orderCreateTo);

        // 制作库存工单
        WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
        wareSkuLockVo.setOrderSn(orderCreateTo.getOrderEntity().getOrderSn());  // 未拆分总订单的sn
        // 保存总订单中所有商品小项的购买详情，ware模块将根据这些信息依次去锁定每一个商品的库存
        List<OrderLockVo> orderLockVoList = orderCreateTo.getOrderItemEntityList().stream().map(orderItemEntity -> {
            OrderLockVo orderLockVo = new OrderLockVo();
            orderLockVo.setSkuId(orderItemEntity.getSkuId());
            orderLockVo.setCount(orderItemEntity.getSkuQuantity());
            orderLockVo.setTitle(orderItemEntity.getSkuName());
            return orderLockVo;
        }).collect(Collectors.toList());
        wareSkuLockVo.setLockList(orderLockVoList);  // 订单中所有商品小项的信息，放到wareSkuLockVo中
        // 该队列将用于处理库存工单
        rabbitTemplate.convertAndSend("stock-event-exchange", "stock.seckill.stock", wareSkuLockVo);

        return orderCreateTo;
    }

    /**
     * 关闭订单
     */
    @Override
    public void closeOrder(OrderTo orderTo) {
        OrderEntity orderEntity = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderTo.getOrderSn()));
        if (orderEntity != null && orderEntity.getStatus().equals(OrderStatusEnum.CREATE_NEW.getCode())) {
            orderEntity.setStatus(OrderStatusEnum.CANCLED.getCode());
            baseMapper.updateById(orderEntity);
        }
        System.out.println("取消" + orderEntity.getId() + "号超时订单");
        // 立刻主动地给库存服务的死信队列发消息，要求立刻解锁库存
        rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other.#", orderTo);
    }

    /**
     * 根据orderSN制作支付vo
     *
     * @param orderSn
     * @return
     */
    @Override
    public PayVo getOrderPay(String orderSn) {
        PayVo payVo = new PayVo();
        OrderEntity order = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));

        if (order == null || order.getStatus() == 4) {
            // 订单已经异常关闭或超时取消
            return null;
        } else {
            BigDecimal payAmount = order.getPayAmount().setScale(2, BigDecimal.ROUND_UP);
            payVo.setTotal_amount(payAmount.toString());
            payVo.setOut_trade_no(orderSn);
            payVo.setSubject("商城支付");
            payVo.setBody("");
            return payVo;
        }
    }

    /**
     * 查询当前用户的所有订单(包括总订单和拆分后的小订单)，支付成功后同样跳转到这个页面
     *
     * @param params
     * @return
     */
    @Override
    public PageUtils queryPageByUser(Map<String, Object> params) {
        // 直接从ThreadLocal中把登录用户的信息拿出来，根据用户信息把大订单查出来
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>().eq("member_id", LoginInterceptor.loginUserThreadLocal.get().getId()).orderByDesc("id")
        );

        // 制作 OrderListVoList
        List<OrderListVo> OrderListVoList = page.getRecords().stream().map(orderEntity -> {
            // 此时每一个orderEntity就是一个未经分割的总订单，要将其对应的切分子订单查出来
            // 查到该总订单的切分关系
            List<OrderSubOrderRelationEntity> currentOrderRelationList =
                    orderSubOrderRelationService.list(new QueryWrapper<OrderSubOrderRelationEntity>().eq("order_sn", orderEntity.getOrderSn()));
            if (currentOrderRelationList.size() == 0 || currentOrderRelationList.size() == 1) {
                // 说明当前总订单并没有切分(要么订单还是在未支付阶段，要么就是子订单只有一个)
                // 那么直接不再去查子订单，此时subOrderVoList字段为空
                OrderListVo orderListVo = new OrderListVo();
                BeanUtils.copyProperties(orderEntity, orderListVo);
                // 然后把当前总订单所有子订单的购物小项都保存在总订单Vo中
                List<OrderItemEntity> currentTopOrderItemList = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_id", orderEntity.getId()));
                orderListVo.setOrderItemEntityList(currentTopOrderItemList);
                orderListVo.setSubOrderVoList(null);
                return orderListVo;
            } else {
                // 说明当前总订单进行了切分，则遍历 currentOrderRelation
                List<OrderItemEntity> currentTopOrderItemList = new ArrayList<>();  // 保存当前总订单的全部商品
                List<SubOrderVo> subOrderVoList = currentOrderRelationList.stream().map(currentOrderRelation -> {
                    SubOrderEntity subOrderEntity = subOrderService.getOne(new QueryWrapper<SubOrderEntity>().eq("order_sn", currentOrderRelation.getSubOrderSn()));
                    SubOrderVo subOrderVo = new SubOrderVo();
                    BeanUtils.copyProperties(subOrderEntity, subOrderVo);
                    List<OrderItemEntity> currentSubOrderItemList = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", subOrderEntity.getOrderSn()));
                    subOrderVo.setOrderItemEntityList(currentSubOrderItemList);
                    currentTopOrderItemList.addAll(currentSubOrderItemList);
                    return subOrderVo;
                }).collect(Collectors.toList());
                OrderListVo orderListVo = new OrderListVo();
                BeanUtils.copyProperties(orderEntity, orderListVo);
                orderListVo.setSubOrderVoList(subOrderVoList);
                orderListVo.setOrderItemEntityList(currentTopOrderItemList);
                return orderListVo;
            }
        }).collect(Collectors.toList());

        // 重新构建一个IPage，因为原来的page只能存放OrderEntity数据，所以需要一个新的IPage存放OrderListVo类
        IPage<OrderListVo> voPage = new Page<>();
        voPage.setRecords(OrderListVoList);
        voPage.setPages(page.getPages());  // 然后其他的设置都从原来的复制过来
        voPage.setCurrent(page.getCurrent());
        voPage.setSize(page.getSize());
        voPage.setTotal(page.getTotal());
        return new PageUtils(voPage);
    }

    /**
     * 接收支付宝的异步消息后进行处理
     * 此时只是签名验证成功，但是不一定是支付成功
     *
     * @param payAsyncVo
     * @return
     */
    @Transactional
    @Override
    public String handlePayedNotify(PayAsyncVo payAsyncVo) {
        // 此时已经属于对订单的核心操作了，因此得上一个锁.是对对应的订单上锁
        RLock orderStatusModifyLock = redissonClient.getLock("order:orderStatusModify:rLock:" + payAsyncVo.getOut_trade_no());
        orderStatusModifyLock.lock(3, TimeUnit.SECONDS);
        try {
            // 1保存交易流水
            PaymentInfoEntity paymentInfoEntity = new PaymentInfoEntity();
            paymentInfoEntity.setAlipayTradeNo(payAsyncVo.getTrade_no());
            paymentInfoEntity.setOrderSn(payAsyncVo.getOut_trade_no());
            paymentInfoEntity.setPaymentStatus(payAsyncVo.getTrade_status());
            paymentInfoEntity.setCallbackTime(payAsyncVo.getNotify_time());
            paymentInfoEntity.setTotalAmount(new BigDecimal(payAsyncVo.getTotal_amount()));
            paymentInfoEntity.setSubject(payAsyncVo.getSubject());
            paymentInfoService.save(paymentInfoEntity);

            // 2拆分订单。只有当支付宝返回支付成功时才会执行这一步，将总订单拆分成子订单
            if (payAsyncVo.getTrade_status().equals("TRADE_SUCCESS") ||
                    payAsyncVo.getTrade_status().equals("TRADE_FINISHED")) {
                // 获得未经拆分的总订单SN 以及对应的总订单本身
                String orderSn = payAsyncVo.getOut_trade_no();
                // 总订单的状态变为已支付
                orderDao.updateOrderStatus(orderSn, OrderStatusEnum.PAYED.getCode());

                OrderEntity topOrderEntity = orderService.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
                // 根据总订单去查 订单小项。然后根据 spu_brand 来进行订单切分
                List<OrderItemEntity> orderItemEntityList = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", orderSn));
                // 保存切分后的子订单
                List<SubOrderEntity> subOrderEntityList = new ArrayList<>();
                // 保存总订单-子订单的关系
                List<OrderSubOrderRelationEntity> orderSubOrderRelationEntityList = new ArrayList<>();

                // 中间HashMap，用于保存品牌 - 订单小项 关系
                HashMap<String, List<OrderItemEntity>> brand_item_map = new HashMap<>();
                for (OrderItemEntity orderItemEntity : orderItemEntityList) {
                    if (!brand_item_map.containsKey(orderItemEntity.getSpuBrand())) {
                        List<OrderItemEntity> tempList = new ArrayList<>();
                        tempList.add(orderItemEntity);
                        brand_item_map.put(orderItemEntity.getSpuBrand(), tempList);
                    } else {
                        brand_item_map.get(orderItemEntity.getSpuBrand()).add(orderItemEntity);
                    }
                }

                // 当前总订单总共被拆分为多少个子订单
                Set<String> allBrand = brand_item_map.keySet();

                if (allBrand.size() == 1) {
                    // 若当前总订单并不需要被拆分，则直接复制总订单为新的子订单，也不更改子订单的ordersn
                    SubOrderEntity subOrderEntity = new SubOrderEntity();
                    BeanUtils.copyProperties(topOrderEntity, subOrderEntity);
                    subOrderEntity.setId(null);
                    subOrderEntity.setFreightAmount(new BigDecimal("0"));
                    subOrderService.save(subOrderEntity);
                    // 保存关系单
                    OrderSubOrderRelationEntity orderSubOrderRelationEntity = new OrderSubOrderRelationEntity();
                    orderSubOrderRelationEntity.setOrderSn(orderSn);
                    orderSubOrderRelationEntity.setSubOrderSn(subOrderEntity.getOrderSn());
                    orderSubOrderRelationService.save(orderSubOrderRelationEntity);
                } else {
                    // 若当前总订单需要被拆分，则为拆分出的每一个子订单赋予新的orderSn
                    for (String brandId : allBrand) {
                        List<OrderItemEntity> currentBrandItem = brand_item_map.get(brandId);
                        String currentSubOrderSn = IdWorker.getTimeId();
                        BigDecimal currentSubOrderAmount = new BigDecimal("0");
                        BigDecimal currentSubOrderIntegrationAmount = new BigDecimal("0");
                        int currentSubOrderIntegration = 0;
                        int currentSubOrderGrowth = 0;

                        // 计算当前子订单 应用平均优惠后的实际总价格
                        // 计算当前子订单 使用的分解后积分
                        // 计算当前子订单 总积分
                        // 计算当前子订单 总成长值
                        // 将商品小项的所属订单更改为当前子订单的订单sn
                        for (OrderItemEntity orderItemEntity : currentBrandItem) {
                            currentSubOrderAmount = currentSubOrderAmount.add(orderItemEntity.getRealAmount());
                            currentSubOrderIntegrationAmount = currentSubOrderIntegrationAmount.add(orderItemEntity.getIntegrationAmount());
                            currentSubOrderIntegration = currentSubOrderIntegration + orderItemEntity.getGiftIntegration();
                            currentSubOrderGrowth = currentSubOrderGrowth + orderItemEntity.getGiftGrowth();
                            orderItemEntity.setOrderSn(currentSubOrderSn);
                            orderItemService.updateById(orderItemEntity);  // 更新绑定为子订单的订单小项
                        }

                        SubOrderEntity subOrderEntity = new SubOrderEntity();
                        // 子订单先直接复制总订单的属性
                        BeanUtils.copyProperties(topOrderEntity, subOrderEntity);
                        // 更改子订单的sn为新产生的sn
                        subOrderEntity.setOrderSn(currentSubOrderSn);
                        // 将子订单的id删除
                        subOrderEntity.setId(null);
                        // 设置子订单的实际支付
                        subOrderEntity.setPayAmount(currentSubOrderAmount);
                        // 子订单的总价设置为0
                        subOrderEntity.setTotalAmount(new BigDecimal("0"));
                        // 子订单运费设置为0
                        subOrderEntity.setFreightAmount(new BigDecimal("0"));
                        // 子订单优惠金额
                        subOrderEntity.setIntegrationAmount(currentSubOrderIntegrationAmount);
                        // 子订单状态此时和总订单保持相同为 1-已付款，0-待收货，0-未删除
                        // 子订单的积分和成长值
                        subOrderEntity.setIntegration(currentSubOrderIntegration);
                        subOrderEntity.setGrowth(currentSubOrderGrowth);
                        // 子订单使用的积分赞设为0
                        subOrderEntity.setUseIntegration(0);

                        subOrderService.save(subOrderEntity);

                        // 保存关系单
                        OrderSubOrderRelationEntity orderSubOrderRelationEntity = new OrderSubOrderRelationEntity();
                        orderSubOrderRelationEntity.setOrderSn(orderSn);
                        orderSubOrderRelationEntity.setSubOrderSn(subOrderEntity.getOrderSn());
                        orderSubOrderRelationService.save(orderSubOrderRelationEntity);
                    }
                }

                // 3.释放锁定的库存，真正扣减库存
                R wareStatus = wareFeignService.handleSuccessPay(payAsyncVo.getOut_trade_no());
                if((int)wareStatus.get("code") != 0){
                    throw new Exception("库存模块Feign接口异常");
                }
            }
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            return "false";
        } finally {
            orderStatusModifyLock.unlock();
        }
    }

    /**
     * 处理秒杀队列中的秒杀订单
     *
     * @param secKillOrderTo 消息队列中的秒杀信息
     */
    @Override
    public void secKillOrder(SecKillOrderTo secKillOrderTo) {
        // 为了简单这里就不真的创建订单了
        long s1 = System.currentTimeMillis();
        System.out.println("后台创建订单==>" + secKillOrderTo.getOrderSn());
        // 延迟模拟订单处理
        int i = 5000000;
        while (i > 0) {
            i--;
        }
        long s2 = System.currentTimeMillis();
        System.out.println("创建订单耗时==>" + (s2 - s1));
    }

    /**
     * 新秒杀逻辑下，监听到消息队列中的 未支付状态的新秒杀订单，将其入库
     *
     * @param orderCreateTo 未支付状态的秒杀订单
     */
    @Override
    @Transactional
    public void saveSeckillNewOrder(OrderCreateTo orderCreateTo) {
        LoginSuccessTo loginSuccessTo = LoginInterceptor.loginUserThreadLocal.get();
        // 需要使用分布式锁，防止多个队列之间处理速度不一致导致的分布式问题。
        // 使用 seckill:order:lock:orderSn:userId 做锁
        RLock order_user_lock = redissonClient.getLock(
                "seckill:order:lock:" + orderCreateTo.getOrderEntity().getOrderSn() + ":" + loginSuccessTo.getId());
        try {
            // 锁的粒度很低，专门的用户专门的订单专门的锁，所以使用tryLock且等待时间为3，不会造成阻塞且超时就意味着另外抢占锁的业务异常
            boolean tryLock = order_user_lock.tryLock(3, TimeUnit.SECONDS);
            // 获取不到锁直接抛出异常就可以了，异常传递到RabbitMQ监听器中，会进行失败回调，消息将被重新放回到消息队列中
            if (!tryLock) {
                throw new RuntimeException("订单被锁定，无法操作订单，请检查链路中其他业务是否异常占用订单锁");
            }
            saveOrder(orderCreateTo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 创建订单的方法。创建订单的核心方法
     * 根据 提交订单 页面传递来的有限的参数，最后一次去同步用户最新的购物车信息，然后创建订单
     * 在这一步中会做 订单小项 真实价格的计算，即把优惠价格加权平均到订单小项上
     * <p>
     * P.S. 后期将秒杀订单的创建也整合到这里。秒杀订单只是不需要去同步购物车信息而已。
     *
     * @return
     */
    private OrderCreateTo createOrderBySubmit(OrderSubmitVo orderSubmitVo) {
        OrderCreateTo orderCreateTo = new OrderCreateTo();

        // 创建一个 未经切分的总订单
        OrderEntity orderEntity = new OrderEntity();

        // 1生成一个订单号,设置基本信息
        // 直接用IdWorker可以快速生成各种Sn
        String orderSn = IdWorker.getTimeId();
        orderEntity.setOrderSn(orderSn);
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());  // 订单状态
        orderEntity.setConfirmStatus(0);
        orderEntity.setDeleteStatus(0);
        orderEntity.setMemberId(LoginInterceptor.loginUserThreadLocal.get().getId());
        orderEntity.setMemberUsername(LoginInterceptor.loginUserThreadLocal.get().getUsername());

        // 2填充收货地址和运费，都是重新查一遍，不是直接用前端传来的
        // 秒不秒杀都重新查询了，想法是此时已经流量很小了，只有和秒杀商品库存相同级别的流量，就无所谓防冲击了
        orderEntity = buildAddressFare(orderSubmitVo, orderEntity);

        // 3填充所有订单小项
        List<OrderItemEntity> orderItemEntityList = bulidOrderItemEntity(orderSn, orderSubmitVo);
        orderCreateTo.setOrderItemEntityList(orderItemEntityList);
        // 如果在非秒杀订单的情况下，购物车被其他登录实例置为空。或者秒杀订单异常为空，那就别算了直接返回
        if (orderItemEntityList == null || orderItemEntityList.size() == 0) {
            orderEntity.setPayAmount(new BigDecimal(0));
            orderCreateTo.setOrderEntity(orderEntity);
            return orderCreateTo;
        } else {
            // 4计算订单价格
            orderCreateTo = computePrice(orderCreateTo, orderEntity, orderItemEntityList, orderSubmitVo);
            return orderCreateTo;
        }
    }

    /**
     * 填充所有 有货 的订单小项
     * 此时订单处于未经切分的状态，所以所有订单小项的orderSn都先写总订单sn
     * <p>
     * P.S. 后期将秒杀订单整合到这里
     *
     * @return
     */
    private List<OrderItemEntity> bulidOrderItemEntity(String orderSn, OrderSubmitVo orderSubmitVo) {
        List<OrderItemVo> orderItemChecked = new ArrayList<>();
        Map<String, Boolean> hasStockMap = new HashMap<>();
        if (orderSubmitVo.getIsSeckill()) {
            // 秒杀订单不再做最终验证，而是直接使用上一步验证完并传递过来的
            OrderItemVo orderItemVo = new OrderItemVo();
            orderItemVo.setSkuId(orderSubmitVo.getSkuId());
            orderItemVo.setTitle(orderSubmitVo.getSecKillProductCacheTo().getSecKillSkuInfoTo().getSkuTitle());
            orderItemVo.setImage(orderSubmitVo.getSecKillProductCacheTo().getSecKillSkuInfoTo().getSkuDefaultImg());
            orderItemVo.setSkuAttr(orderSubmitVo.getSecKillProductCacheTo().getSecKillSkuInfoTo().getSkuAttr());
            orderItemVo.setPrice(orderSubmitVo.getSecKillProductCacheTo().getSecKillProductTo().getSeckillPrice());
            orderItemVo.setCount(orderSubmitVo.getWantNum());
            orderItemVo.setHasStock(true);
            orderItemVo.setBrandId(orderSubmitVo.getSecKillProductCacheTo().getSecKillSkuInfoTo().getBrandId());
            orderItemVo.setBrandName(orderSubmitVo.getSecKillProductCacheTo().getSecKillSkuInfoTo().getBrandName());
            orderItemChecked.add(orderItemVo);
            // 秒杀订单不做有货检验，直接认为有货
            hasStockMap.put(orderSubmitVo.getSkuId().toString(), true);
        } else {
            // 普通订单还是会远程调用从购物车中把当前订单的所有商品拿来
            // 此时最后更新一次最新价格
            orderItemChecked = cartFeignService.getOrderCartChecked();
            // 以及最后一次更新商品的库存情况
            List<Long> idList = orderItemChecked.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());
            R r = wareFeignService.hasStock(idList);
            hasStockMap = (Map<String, Boolean>) r.get("data");
        }

        if (orderItemChecked != null && orderItemChecked.size() > 0) {
            // 普通订单，把通过 有货检验 商品的Vo重新构建成保存在数据库中的Entity
            // 秒杀订单，不再进行有货检验，直接构建
            Map<String, Boolean> finalHasStockMap = hasStockMap;
            List<OrderItemEntity> orderItemEntityList = orderItemChecked.stream().filter(orderItemVo -> {
                return finalHasStockMap.get(orderItemVo.getSkuId().toString());
            }).map(orderItemVo -> {
                OrderItemEntity orderItemEntity = new OrderItemEntity();
                // 1订单小项所属的未经切分的总订单号
                orderItemEntity.setOrderSn(orderSn);

                // 2sku商品的spu信息
                SpuInfoVo spuInfoVo = productFeignService.spuByskuId(orderItemVo.getSkuId());
                orderItemEntity.setSpuId(spuInfoVo.getId());
                orderItemEntity.setSpuName(spuInfoVo.getSpuName());
                orderItemEntity.setSpuBrand(spuInfoVo.getBrandId().toString());
                orderItemEntity.setCategoryId(spuInfoVo.getCatalogId());

                // 3sku商品积分信息。商品积分信息是由spu+买了多少商品决定的。远程调用去查
                BigDecimal[] boundsBySpuId = couponFeignService.getBoundsBySpuId(spuInfoVo.getId());
                if (boundsBySpuId != null) {
                    orderItemEntity.setGiftGrowth(boundsBySpuId[0].multiply(new BigDecimal(orderItemVo.getCount().toString())).intValue());
                    orderItemEntity.setGiftIntegration(boundsBySpuId[1].multiply(new BigDecimal(orderItemVo.getCount().toString())).intValue());
                } else {
                    orderItemEntity.setGiftGrowth(0);
                    orderItemEntity.setGiftIntegration(0);
                }

                // 4商品sku信息
                orderItemEntity.setSkuId(orderItemVo.getSkuId());
                orderItemEntity.setSkuName(orderItemVo.getTitle());
                orderItemEntity.setSkuPic(orderItemVo.getImage());
                orderItemEntity.setSkuPrice(orderItemVo.getPrice());
                orderItemEntity.setSkuQuantity(orderItemVo.getCount());
                orderItemEntity.setSkuAttrsVals(StringUtils.collectionToDelimitedString(orderItemVo.getSkuAttr(), ";"));

                // 5商品sku的原始价格，先不管使用了优惠的事情
                orderItemEntity.setRealAmount(orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString())));

                return orderItemEntity;
            }).collect(Collectors.toList());
            return orderItemEntityList;
        }
        return null;
    }

    /**
     * 填充地址和运费
     *
     * @param orderSubmitVo
     * @param orderEntity
     * @return
     */
    private OrderEntity buildAddressFare(OrderSubmitVo orderSubmitVo, OrderEntity orderEntity) {
        MemberAddressVo addressVo = memberFeignService.getAddressId(orderSubmitVo.getAddrId());
        orderEntity.setReceiverProvince(addressVo.getProvince());
        orderEntity.setReceiverCity(addressVo.getCity());
        orderEntity.setReceiverRegion(addressVo.getRegion());
        orderEntity.setReceiverDetailAddress(addressVo.getDetailAddress());
        orderEntity.setReceiverName(addressVo.getName());
        orderEntity.setReceiverPhone(addressVo.getPhone());
        orderEntity.setReceiverPostCode(addressVo.getPostCode());

        R getfare = memberFeignService.getFare(orderSubmitVo.getAddrId());
        Integer dara = (Integer) getfare.get("fare");
        BigDecimal fare = new BigDecimal(dara.toString());
        orderEntity.setFreightAmount(fare);

        return orderEntity;
    }


    /**
     * 计算未经拆分的总订单价格
     * 同时还要计算 每个订单小项 运用上整体优惠之后的实际价格
     *
     * @param orderEntity
     * @param orderItemEntityList
     */
    private OrderCreateTo computePrice(OrderCreateTo orderCreateTo, OrderEntity orderEntity, List<OrderItemEntity> orderItemEntityList, OrderSubmitVo orderSubmitVo) {
        if (orderItemEntityList == null || orderItemEntityList.size() == 0) {
            orderCreateTo.setOrderEntity(orderEntity);
            orderCreateTo.setOrderItemEntityList(orderItemEntityList);
            return orderCreateTo;
        }
        BigDecimal orderTotal = new BigDecimal("0");
        Integer orderGrowth = 0;
        Integer orderIntegration = 0;
        for (OrderItemEntity orderItemEntity : orderItemEntityList) {
            BigDecimal skuRealPrice = orderItemEntity.getRealAmount();
            orderTotal = orderTotal.add(skuRealPrice);
            orderGrowth = orderItemEntity.getGiftGrowth() + orderGrowth;
            orderIntegration = orderItemEntity.getGiftIntegration() + orderIntegration;
        }
        // 订单总额就是不算各种优惠和运费的总额
        orderEntity.setTotalAmount(orderTotal);
        // 订单使用的积分/折扣金额，这个就是根据前端传来的
        orderEntity.setUseIntegration(orderSubmitVo.getIntegration());
        orderEntity.setIntegrationAmount(orderSubmitVo.getIntegrationPrice());
        // 订单应付总金额=原价-折扣+运费
        orderEntity.setPayAmount(orderEntity.getTotalAmount().add(orderEntity.getFreightAmount()).subtract(orderEntity.getIntegrationAmount()));
        // 各个订单小项实际金额 = 原价-折扣加权平均, 各个订单小项实际消耗积分=总消耗加权平均
        if (orderTotal.compareTo(new BigDecimal("0")) != 0) {
            BigDecimal finalOrderTotal = orderTotal;
            orderItemEntityList.forEach(orderItemEntity -> {
                // 分解后实际消耗积分金额
                BigDecimal realIntegrationAfterYouHui = (orderSubmitVo.getIntegrationPrice()
                        .multiply(orderItemEntity.getRealAmount()
                                .divide(finalOrderTotal, BigDecimal.ROUND_HALF_UP)))
                        .setScale(2, BigDecimal.ROUND_HALF_UP);

                // 分解后实际金额
                BigDecimal realAmountAfterYouHui = orderItemEntity.getRealAmount()
                        .subtract(realIntegrationAfterYouHui);

                orderItemEntity.setRealAmount(realAmountAfterYouHui);
                orderItemEntity.setIntegrationAmount(realIntegrationAfterYouHui);
            });
        }
        // 订单的总积分，将所有sku加起来
        orderEntity.setGrowth(orderGrowth);
        orderEntity.setIntegration(orderIntegration);
        orderCreateTo.setOrderEntity(orderEntity);
        orderCreateTo.setOrderItemEntityList(orderItemEntityList);
        return orderCreateTo;
    }


    /**
     * 保存未经拆分的总订单数据
     *
     * @param orderCreateTo
     */
    public void saveOrder(OrderCreateTo orderCreateTo) {
        OrderEntity orderEntity = orderCreateTo.getOrderEntity();
        orderEntity.setCreateTime(new Date());
        orderEntity.setModifyTime(new Date());
        // ES在同步可能出现得到总订单，但商品小项还没入库的情况
        // 所以使用事务(在调用该方法的父方法上使用了事务注解)，这样可以保证当ES发现总订单可见时，子订单一定可见，所以用了事务之后，就可以先保存总订单。
        // 而且由于mybatis的save是基于MySQL自身的主键自增原理的，所以不会出现并发性问题(但是并发性下主键可以自增，但不一定保证连续。这是MySQL自身的问题，牺牲连续性来保证并发性)
        // 当然这里的无并发是因为这里只做插入所以才不会

        // 查看是否因为速度不一致导致其他状态先行处理完毕
        // 如果已存在则不做任何操作，包括订单小项。因为别的状态也是事务处理，所以一定是总订单和小项一起完成。
        OrderEntity isExisted = this.getOne(new QueryWrapper<OrderEntity>().eq("orderSn", orderCreateTo.getOrderEntity().getOrderSn()));
        if(isExisted != null){
            // 保存大订单，自动填充主键。MySQL事务保证之后插入的数据不管是不是用事务，都不会占用这里自动填充的主键
            orderService.save(orderEntity);
            List<OrderItemEntity> orderItemEntityList = orderCreateTo.getOrderItemEntityList();
            // 修改商品小项中所属总订单orderId的信息
            orderItemEntityList.forEach(orderItemEntity -> {
                orderItemEntity.setOrderId(orderEntity.getId());
            });
            orderItemService.saveBatch(orderItemEntityList);
        }
    }


    /**
     * 确认收货。不管是手动确认还是自动确认都调用此方法
     *
     * @param orderSN
     * @return
     */
    @Override
    public R confirmOrder(String orderSN) {
        // 确认收货不需要考虑幂等性
        // 只在全局上，限制重复直连迸发和异常查询。且使用用户Id做锁
        LoginSuccessTo loginSuccessTo = LoginInterceptor.loginUserThreadLocal.get();
        RRateLimiter orderConfirmAbnormalLock = redissonClient.getRateLimiter("order:orderConfirmAbnormal:rLock:" + loginSuccessTo.getId());
        // 2分钟内5次就限制
        orderConfirmAbnormalLock.trySetRate(RateType.OVERALL, 5, 2, RateIntervalUnit.MINUTES);

        boolean tryAcquire = orderConfirmAbnormalLock.tryAcquire();

        // 如果已经被限制
        if (!tryAcquire) {
            return R.error(1, "由于您多次出现异常行为，您对该接口的操作权限已经被锁定。请三分钟后重试");
        } else {
            // 必须 是本人，且处于 已付款/已发货状态才行
            OrderEntity orderConfirm = orderService.getOne(new QueryWrapper<OrderEntity>()
                    .eq("member_id", loginSuccessTo.getId())
                    .eq("order_sn", orderSN)
                    .and((orderEntityQueryWrapper -> {
                        orderEntityQueryWrapper.eq("status", 1).or().eq("status", 2);
                    })));

            // 确实存在符合要求的订单
            if (orderConfirm != null) {
                orderConfirm.setStatus(3);
                orderService.updateById(orderConfirm);
                return R.ok("确认收货成功");
            } else {
                // 异常查询，扣减
                orderConfirmAbnormalLock.tryAcquire();
                return R.error(2, "您提交了错误的订单代码。请勿持续攻击该接口，否则可能导致您的账号被封禁");
            }
        }
    }

    /**
     * 当未支付的总订单入库时，Canal监听到通知这里，由这个方法构建 OrderListTo
     * 注意由于RabbitMQ的滞后性，因此必须要根据总订单的状态进一步确认
     * 如果总订单已经处于完成支付的状态，则逻辑是不一样的。
     *
     * @param topOrderId
     * @param topOrderSn
     * @return
     */
    @Override
    public OrderListTo getNewOrderAsync(Long topOrderId, String topOrderSn) {
        OrderEntity orderEntity = orderService.getOne(new QueryWrapper<OrderEntity>().eq("id", topOrderId));
        if (orderEntity != null && orderEntity.getOrderSn().equals(topOrderSn)) {
            // 如果订单确实处于待支付或者支付已取消的状态
            if (orderEntity.getStatus() == 0 || orderEntity.getStatus() == 1) {
                OrderListTo orderListTo = new OrderListTo();
                BeanUtils.copyProperties(orderEntity, orderListTo);
                // 当前总订单的商品小项
                List<OrderItemEntity> orderItemEntityList =
                        orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", topOrderSn));
                List<OrderItemEntityTo> itemEntityToList = orderItemEntityList.stream().map(orderItemEntity -> {
                    OrderItemEntityTo orderItemEntityTo = new OrderItemEntityTo();
                    BeanUtils.copyProperties(orderItemEntity, orderItemEntityTo);
                    return orderItemEntityTo;
                }).collect(Collectors.toList());
                orderListTo.setOrderItemEntityList(itemEntityToList);
                return orderListTo;
            } else {
                // 否则订单已经以极快的速度完成支付，此时需要按照构建完成支付订单的逻辑构建
                return getUpdateOrderAsync(topOrderId, topOrderSn);
            }
        } else {
            return null;
        }
    }

    /**
     * 当有一个总订单完成支付时，Canal监听到数据库的更改来到这里，由这个方法构建OrderListTo
     * 当然，这个方法也可能在getNewOrderAsync()中被调用
     *
     * @param topOrderId
     * @param topOrderSn
     * @return
     */
    @Override
    public OrderListTo getUpdateOrderAsync(Long topOrderId, String topOrderSn) {
        OrderEntity orderEntity = orderService.getOne(new QueryWrapper<OrderEntity>().eq("id", topOrderId));
        if (orderEntity != null && orderEntity.getOrderSn().equals(topOrderSn)) {
            // 调用此方法时一定完成了订单切分，要将其对应的切分子订单查出来
            // 查到该总订单的切分关系
            List<OrderSubOrderRelationEntity> currentOrderRelationList =
                    orderSubOrderRelationService.list(new QueryWrapper<OrderSubOrderRelationEntity>().eq("order_sn", orderEntity.getOrderSn()));
            if (currentOrderRelationList.size() == 0 || currentOrderRelationList.size() == 1) {
                // 说明当前总订单只拆分成了一个子订单
                // 那么直接不再去查子订单，此时subOrderVoList字段为空
                OrderListTo orderListTo = new OrderListTo();
                BeanUtils.copyProperties(orderEntity, orderListTo);
                // 然后把当前总订单所有子订单的购物小项都保存在总订单Vo中
                List<OrderItemEntity> currentTopOrderItemList = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_id", orderEntity.getId()));
                List<OrderItemEntityTo> itemEntityToList = currentTopOrderItemList.stream().map(orderItemEntity -> {
                    OrderItemEntityTo orderItemEntityTo = new OrderItemEntityTo();
                    BeanUtils.copyProperties(orderItemEntity, orderItemEntityTo);
                    return orderItemEntityTo;
                }).collect(Collectors.toList());
                orderListTo.setOrderItemEntityList(itemEntityToList);
                orderListTo.setSubOrderVoList(null);
                return orderListTo;
            } else {
                // 说明当前总订单切分成了至少两个子订单，则遍历 currentOrderRelation
                List<OrderItemEntityTo> currentTopOrderItemList = new ArrayList<>();  // 保存当前总订单的全部商品
                List<SubOrderTo> subOrderToList = currentOrderRelationList.stream().map(currentOrderRelation -> {
                    SubOrderEntity subOrderEntity =
                            subOrderService.getOne(new QueryWrapper<SubOrderEntity>().eq("order_sn", currentOrderRelation.getSubOrderSn()));
                    SubOrderTo subOrderTo = new SubOrderTo();
                    BeanUtils.copyProperties(subOrderEntity, subOrderTo);
                    List<OrderItemEntity> currentSubOrderItemList =
                            orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", subOrderEntity.getOrderSn()));
                    List<OrderItemEntityTo> itemEntityToList = currentSubOrderItemList.stream().map(orderItemEntity -> {
                        OrderItemEntityTo orderItemEntityTo = new OrderItemEntityTo();
                        BeanUtils.copyProperties(orderItemEntity, orderItemEntityTo);
                        return orderItemEntityTo;
                    }).collect(Collectors.toList());
                    subOrderTo.setOrderItemEntityList(itemEntityToList);
                    currentTopOrderItemList.addAll(itemEntityToList);
                    return subOrderTo;
                }).collect(Collectors.toList());

                OrderListTo orderListTo = new OrderListTo();
                BeanUtils.copyProperties(orderEntity, orderListTo);
                orderListTo.setSubOrderVoList(subOrderToList);
                orderListTo.setOrderItemEntityList(currentTopOrderItemList);
                return orderListTo;
            }
        } else {
            return null;
        }
    }
}